﻿using System;
using System.IO;

namespace Ucto.ToExcelConvertor
{
	/// <summary>
	/// Data reader is wrapper over the file. Immutable. Opens the handle to the file in constructor.
	/// Do not forget to dispose. Also in unit tests.
	/// </summary>
	public class DataReader : IDataReader
	{

		/// <summary>
		/// Opens the specified file for reading.
		/// </summary>
		/// <param name="path">The path to the input data file.</param>
		public static DataReader Open(string path)
		{
			return new DataReader(path);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DataReader"/> class.
		/// </summary>
		/// <param name="path">The path to the input data file.</param>
		protected DataReader(string path)
		{
			if (!File.Exists(path))
			{
				throw new Exception(
					string.Format("Given file {0} does not exist.", path)
					);
			}

			this.StreamReader = File.OpenText(path);
		}

		/// <summary>
		/// Gets the stream reader including stream over accessed file.
		/// </summary>
		protected StreamReader StreamReader { get; private set; }

		/// <summary>
		/// Gets the header of the file.
		/// Call <see cref="EnsureHeaderLoaded"/> to load the header in advance.
		/// </summary>
		protected DataHeader DataHeader { get; private set; }

		/// <summary>
		/// Checks if the header of the input data has been loaded.
		/// </summary>
		protected bool IsHeaderLoaded
		{
			get
			{
				return DataHeader != null;
			}
		}

		/// <summary>
		/// Reads and returns header information from the input data.
		/// </summary>
		public DataHeader GetHeader()
		{
			EnsureHeaderLoaded();
			return DataHeader;
		}

		/// <summary>
		/// Reads the header information from the data.
		/// </summary>
		protected DataHeader ReadHeader()
		{
			var result = new DataHeader();

			try
			{
				string input = null;

				// row count
				input = StreamReader.ReadLine().Trim();
				result.RowCount = int.Parse(input);

				// column count
				input = StreamReader.ReadLine().Trim();
				result.ColumnCount = int.Parse(input);

				// value length
				input = StreamReader.ReadLine().Trim();
				result.ValueLenght = int.Parse(input);
			}
			catch (Exception ex)
			{
				throw new Exception("Error while reading header.", ex);
			}

			return result;
		}

		/// <summary>
		/// Loads header information from the data file. Reading will be performed only once!
		/// </summary>
		protected void EnsureHeaderLoaded()
		{
			if (!IsHeaderLoaded)
			{
				DataHeader = ReadHeader();
			}
		}

		/// <summary>
		/// Frees underlying resources (file stream).
		/// </summary>
		public void Dispose()
		{
			if (this.StreamReader != null)
			{
				(this.StreamReader as IDisposable).Dispose();
				this.StreamReader = null;
			}
		}

		/// <summary>
		/// From opened stream reads the next value. Then returns the value.
		/// Lenght of the readed value is specified in the header.
		/// </summary>
		public string GetNextValue()
		{
			EnsureHeaderLoaded();

			int count = DataHeader.ValueLenght;

			if (StreamReader.EndOfStream)
			{
				return null;
			}

			char[] buffer = new char[count];
			var result = StreamReader.Read(
				buffer
				, 0 // this is offset, not index
				, count
				);

			return new String(buffer);
		}
	}
}
